This notebook summarizes the findings from assigning consensus cell type labels to all ScPCA samples. All results from the cell-type-consensus module in OpenScPCA-nf must be saved to results prior to rendering this notebook.

suppressPackageStartupMessages({
  # load required packages
  library(ggplot2)
})

# Set default ggplot theme
theme_set(
  theme_classic()
)

Functions

# function to read in project data frames with all cells in a project
# output is a summarized table with total cells per sample, total cells per annotation, and number of cell types 
summarize_celltypes <- function(file, id){
  
  # read in data
  df <- readr::read_tsv(file) 
  
  # get total cell count and number of assigned cell types per library
  total_cells_df <- df |> 
    dplyr::group_by(library_id) |> 
    dplyr::summarize(
      total_cells_per_library = length(library_id),
      num_celltypes = length(unique(consensus_annotation))
    )
  
  summary_df <- df |> 
    dplyr::group_by(library_id, consensus_annotation, consensus_ontology) |> 
    dplyr::summarize(total_cells_per_annotation = length(consensus_annotation)) |>
    dplyr::left_join(total_cells_df, by = "library_id") |> 
    dplyr::mutate(
      # add percentage 
      percent_cells_annotation = round((total_cells_per_annotation / total_cells_per_library) * 100, 2)
    ) |> 
    dplyr::ungroup()
  
  return(summary_df)
  
}

Data setup

# The base path for the OpenScPCA repository, found by its (hidden) .git directory
repository_base <- rprojroot::find_root(rprojroot::is_git_root)
module_base <- file.path(repository_base, "analyses", "cell-type-consensus")

# results directory with cell-type-consensus 
results_dir <- file.path(module_base, "results", "cell-type-consensus")

# diagnoses table used for labeling plots 
diagnoses_file <- file.path(module_base, "sample-info", "project-diagnoses.tsv")
# list all results files 
results_files <- list.files(results_dir, pattern = "_consensus-cell-types\\.tsv.\\gz$", full.names = TRUE)

# get project ids from file list  
project_ids <- stringr::str_remove(basename(results_files), "_consensus-cell-types.tsv.gz")
names(results_files) <- project_ids

# remove cell line projects from file list
cell_line_projects <- c("SCPCP000020", "SCPCP000024")
project_ids <- setdiff(project_ids, cell_line_projects) # remove cell line projects
results_files <- results_files[project_ids]
# read in diagnoses
diagnoses_df <- readr::read_tsv(diagnoses_file)


# read in results and prep data frame for plotting 
all_results_df <- results_files |> 
  purrr::imap(summarize_celltypes) |> 
  dplyr::bind_rows(.id = "project_id") |> 
  # add in diagnoses 
  dplyr::left_join(diagnoses_df, by = "project_id") |> 
  dplyr::mutate(
    # create a label for plotting
    project_label = glue::glue("{project_id}:{diagnosis}")
  )

Is it all just Unknown?

The first thing we will look at is how many of the cells in each sample are categorized as “Unknown”, which means no consensus between SingleR and CellAssign was identified.

unknown_only <- all_results_df |> 
  dplyr::filter(consensus_annotation == "Unknown")

ggplot(unknown_only, aes(x = project_label, y = percent_cells_annotation)) +
  ggforce::geom_sina(size = 0.1) +
  theme(axis.text.x = element_text(angle = 90, hjust = 1, vjust = 0.5),
        plot.margin = margin(10,10,10,10)) +
  labs(
    x = "", 
    y = "Percent of cells annotated as Unknown"
  )

NA

It looks like we do have some samples that aren’t just all “Unknown”! It definitely varies by project, but for most projects we at least see some proportion of samples with assigned cell types.

Let’s look at how many samples actually have some cells outside of unknown identified. To do this, we will identify all libraries that only have cells called as “Unknown”.

high_tumor_df <- unknown_only |> 
  dplyr::mutate(no_cells_identified = percent_cells_annotation == 100) |> 
  dplyr::group_by(project_label) |> 
  dplyr::summarize(all_unknown = sum(no_cells_identified),
                   classified_cells = sum(!no_cells_identified),
                   percentage_unknown = round(all_unknown/(all_unknown + classified_cells)*100, 2),
                   # add number of libraries for plotting 
                   total_libraries = length(library_id)) |>
  # set order for plots 
  dplyr::mutate(project_label = forcats::fct_reorder(project_label, total_libraries, .desc = TRUE))

Which projects have the highest proportion of samples with all “Unknown”?

# table with percentage of samples 
high_tumor_df |> 
  dplyr::select(project_label, percentage_unknown) |> 
  dplyr::arrange(desc(percentage_unknown))
NA

It looks like all projects do have cell types identified that are not “Unknown”. However, SCPCP000011 (retinoblastoma), has a fairly high percentage of samples without any consensus labels.

Number of cell types observed

Below we look at the number of cell types observed in each project for all samples. This does not include cells labeled as “Unknown”.

num_celltypes_df <- all_results_df |> 
  # add a new line for facet labels 
  dplyr::mutate(facet_label = glue::glue("{project_id}\n{diagnosis}")) |>
  # remove unknown as a cell type 
  dplyr::filter(consensus_annotation != "Unknown") |> 
  dplyr::select(facet_label, library_id, num_celltypes) |> 
  unique()

ggplot(num_celltypes_df, aes(x = num_celltypes)) +
  geom_histogram(binwidth = 1, center = 0) +
  facet_wrap(vars(facet_label), 
             ncol = 3) +
  labs(
    x = "Number of cell types"
  ) +
  theme_bw()

Distribution of consensus cell types

Now we look at the distribution of the cell types in each sample. For these plots, we will pull out the top 9 cell types for each project. All other cells will be labeled with “All remaining cell types”.

The top cell types are determined by counting how many libraries each cell type is found in within a project and taking the most frequent types.

plot_df <- all_results_df |> 
    dplyr::group_by(project_id) |> 
    dplyr::mutate(
      # get most frequently observed cell types across libraries in that project 
      top_celltypes = forcats::fct_lump_n(consensus_annotation, 9, other_level = "All remaining cell types", ties.method = "first") |> 
        # sort by frequency 
        forcats::fct_infreq() |> 
        # make sure all remaining and unknown are last, use this to assign colors in specific order
        forcats::fct_relevel("All remaining cell types", "Unknown", after = Inf)
    )
Warning: There was 1 warning in `dplyr::mutate()`.
ℹ In argument: `top_celltypes = forcats::fct_relevel(...)`.
ℹ In group 19: `project_id = "SCPCP000021"`.
Caused by warning:
! 1 unknown level in `f`: All remaining cell types
# get all unique cell types ordered by frequency 
unique_celltypes <- plot_df |> 
  dplyr::filter(!top_celltypes %in% c("All remaining cell types", "Unknown")) |> 
  dplyr::pull(top_celltypes) |> 
  unique() |>
  sort() |> 
  as.character()

# get color palette
colors <- c(
  palette.colors(palette = "alphabet"),
  "black", # 1 extra since alphabet is 26 and we have 27, this will be plasma cell which shows up once 
  "grey60", 
  "grey95"
)
names(colors) <- c(unique_celltypes, "All remaining cell types", "Unknown")
project_labels <- unique(all_results_df$project_label)

# stacked bar chart showing the distribution of the top 9 cell types for each project, including Unknown
project_labels |> 
  purrr::map(\(label){
    
    project_df <- plot_df |> 
      dplyr::filter(project_label == label) |> 
      dplyr::mutate(
        # relevel factors for specific project 
        top_celltypes = forcats::fct_infreq(top_celltypes) |> 
          forcats::fct_relevel("All remaining cell types", "Unknown", after = Inf)
      )
    
    # make a stacked bar chart with top cell types 
    ggplot(project_df) + 
      aes(
        x = library_id, 
        y = percent_cells_annotation, 
        fill = top_celltypes
      ) +
      geom_col() + 
      scale_y_continuous(expand = c(0,0)) +
      scale_fill_manual(values = colors, name = "cell type") +
      ggtitle(label) +
      theme(axis.text.x = element_blank())
  
    }) |>
  patchwork::wrap_plots(ncol = 1)

This looks really promising! A few observations:

Most frequently observed cell types

The last thing we will do is look at the most frequently observed cell types across all samples. The below table is ordered by the number of libraries the cell type is observed.

all_results_df |> 
  dplyr::filter(consensus_annotation != "Unknown") |> 
  dplyr::group_by(consensus_annotation) |> 
  dplyr::summarize(
    total_libraries = dplyr::n(),
    min_percentage = min(percent_cells_annotation),
    mean_percentage = round(mean(percent_cells_annotation), 2),
    median_percentage = median(percent_cells_annotation),
    max_percentage = max(percent_cells_annotation)
  ) |> 
  dplyr::arrange(desc(total_libraries))
NA

Session info

# record the versions of the packages used in this analysis and other environment information
sessionInfo()
LS0tCnRpdGxlOiAiRXhwbG9yZSBjb25zZW5zdXMgY2VsbCB0eXBlcyIKYXV0aG9yOiBBbGx5IEhhd2tpbnMKZGF0ZTogImByIFN5cy5EYXRlKClgIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKLS0tCgpUaGlzIG5vdGVib29rIHN1bW1hcml6ZXMgdGhlIGZpbmRpbmdzIGZyb20gYXNzaWduaW5nIGNvbnNlbnN1cyBjZWxsIHR5cGUgbGFiZWxzIHRvIGFsbCBTY1BDQSBzYW1wbGVzLiAKQWxsIHJlc3VsdHMgZnJvbSB0aGUgYGNlbGwtdHlwZS1jb25zZW5zdXNgIG1vZHVsZSBpbiBgT3BlblNjUENBLW5mYCBtdXN0IGJlIHNhdmVkIHRvIGByZXN1bHRzYCBwcmlvciB0byByZW5kZXJpbmcgdGhpcyBub3RlYm9vay4gCgpgYGB7ciBwYWNrYWdlc30Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKHsKICAjIGxvYWQgcmVxdWlyZWQgcGFja2FnZXMKICBsaWJyYXJ5KGdncGxvdDIpCn0pCgojIFNldCBkZWZhdWx0IGdncGxvdCB0aGVtZQp0aGVtZV9zZXQoCiAgdGhlbWVfY2xhc3NpYygpCikKYGBgCgojIyBGdW5jdGlvbnMgCgpgYGB7cn0KIyBmdW5jdGlvbiB0byByZWFkIGluIHByb2plY3QgZGF0YSBmcmFtZXMgd2l0aCBhbGwgY2VsbHMgaW4gYSBwcm9qZWN0CiMgb3V0cHV0IGlzIGEgc3VtbWFyaXplZCB0YWJsZSB3aXRoIHRvdGFsIGNlbGxzIHBlciBzYW1wbGUsIHRvdGFsIGNlbGxzIHBlciBhbm5vdGF0aW9uLCBhbmQgbnVtYmVyIG9mIGNlbGwgdHlwZXMgCnN1bW1hcml6ZV9jZWxsdHlwZXMgPC0gZnVuY3Rpb24oZmlsZSwgaWQpewogIAogICMgcmVhZCBpbiBkYXRhCiAgZGYgPC0gcmVhZHI6OnJlYWRfdHN2KGZpbGUpIAogIAogICMgZ2V0IHRvdGFsIGNlbGwgY291bnQgYW5kIG51bWJlciBvZiBhc3NpZ25lZCBjZWxsIHR5cGVzIHBlciBsaWJyYXJ5CiAgdG90YWxfY2VsbHNfZGYgPC0gZGYgfD4gCiAgICBkcGx5cjo6Z3JvdXBfYnkobGlicmFyeV9pZCkgfD4gCiAgICBkcGx5cjo6c3VtbWFyaXplKAogICAgICB0b3RhbF9jZWxsc19wZXJfbGlicmFyeSA9IGxlbmd0aChsaWJyYXJ5X2lkKSwKICAgICAgbnVtX2NlbGx0eXBlcyA9IGxlbmd0aCh1bmlxdWUoY29uc2Vuc3VzX2Fubm90YXRpb24pKQogICAgKQogIAogIHN1bW1hcnlfZGYgPC0gZGYgfD4gCiAgICBkcGx5cjo6Z3JvdXBfYnkobGlicmFyeV9pZCwgY29uc2Vuc3VzX2Fubm90YXRpb24sIGNvbnNlbnN1c19vbnRvbG9neSkgfD4gCiAgICBkcGx5cjo6c3VtbWFyaXplKHRvdGFsX2NlbGxzX3Blcl9hbm5vdGF0aW9uID0gbGVuZ3RoKGNvbnNlbnN1c19hbm5vdGF0aW9uKSkgfD4KICAgIGRwbHlyOjpsZWZ0X2pvaW4odG90YWxfY2VsbHNfZGYsIGJ5ID0gImxpYnJhcnlfaWQiKSB8PiAKICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICMgYWRkIHBlcmNlbnRhZ2UgCiAgICAgIHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbiA9IHJvdW5kKCh0b3RhbF9jZWxsc19wZXJfYW5ub3RhdGlvbiAvIHRvdGFsX2NlbGxzX3Blcl9saWJyYXJ5KSAqIDEwMCwgMikKICAgICkgfD4gCiAgICBkcGx5cjo6dW5ncm91cCgpCiAgCiAgcmV0dXJuKHN1bW1hcnlfZGYpCiAgCn0KYGBgCgojIyBEYXRhIHNldHVwCgoKYGBge3IgYmFzZSBwYXRoc30KIyBUaGUgYmFzZSBwYXRoIGZvciB0aGUgT3BlblNjUENBIHJlcG9zaXRvcnksIGZvdW5kIGJ5IGl0cyAoaGlkZGVuKSAuZ2l0IGRpcmVjdG9yeQpyZXBvc2l0b3J5X2Jhc2UgPC0gcnByb2pyb290OjpmaW5kX3Jvb3QocnByb2pyb290Ojppc19naXRfcm9vdCkKbW9kdWxlX2Jhc2UgPC0gZmlsZS5wYXRoKHJlcG9zaXRvcnlfYmFzZSwgImFuYWx5c2VzIiwgImNlbGwtdHlwZS1jb25zZW5zdXMiKQoKIyByZXN1bHRzIGRpcmVjdG9yeSB3aXRoIGNlbGwtdHlwZS1jb25zZW5zdXMgCnJlc3VsdHNfZGlyIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInJlc3VsdHMiLCAiY2VsbC10eXBlLWNvbnNlbnN1cyIpCgojIGRpYWdub3NlcyB0YWJsZSB1c2VkIGZvciBsYWJlbGluZyBwbG90cyAKZGlhZ25vc2VzX2ZpbGUgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAic2FtcGxlLWluZm8iLCAicHJvamVjdC1kaWFnbm9zZXMudHN2IikKYGBgCgpgYGB7cn0KIyBsaXN0IGFsbCByZXN1bHRzIGZpbGVzIApyZXN1bHRzX2ZpbGVzIDwtIGxpc3QuZmlsZXMocmVzdWx0c19kaXIsIHBhdHRlcm4gPSAiX2NvbnNlbnN1cy1jZWxsLXR5cGVzXFwudHN2LlxcZ3okIiwgZnVsbC5uYW1lcyA9IFRSVUUpCgojIGdldCBwcm9qZWN0IGlkcyBmcm9tIGZpbGUgbGlzdCAgCnByb2plY3RfaWRzIDwtIHN0cmluZ3I6OnN0cl9yZW1vdmUoYmFzZW5hbWUocmVzdWx0c19maWxlcyksICJfY29uc2Vuc3VzLWNlbGwtdHlwZXMudHN2Lmd6IikKbmFtZXMocmVzdWx0c19maWxlcykgPC0gcHJvamVjdF9pZHMKCiMgcmVtb3ZlIGNlbGwgbGluZSBwcm9qZWN0cyBmcm9tIGZpbGUgbGlzdApjZWxsX2xpbmVfcHJvamVjdHMgPC0gYygiU0NQQ1AwMDAwMjAiLCAiU0NQQ1AwMDAwMjQiKQpwcm9qZWN0X2lkcyA8LSBzZXRkaWZmKHByb2plY3RfaWRzLCBjZWxsX2xpbmVfcHJvamVjdHMpICMgcmVtb3ZlIGNlbGwgbGluZSBwcm9qZWN0cwpyZXN1bHRzX2ZpbGVzIDwtIHJlc3VsdHNfZmlsZXNbcHJvamVjdF9pZHNdCmBgYAoKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQojIHJlYWQgaW4gZGlhZ25vc2VzCmRpYWdub3Nlc19kZiA8LSByZWFkcjo6cmVhZF90c3YoZGlhZ25vc2VzX2ZpbGUpCgoKIyByZWFkIGluIHJlc3VsdHMgYW5kIHByZXAgZGF0YSBmcmFtZSBmb3IgcGxvdHRpbmcgCmFsbF9yZXN1bHRzX2RmIDwtIHJlc3VsdHNfZmlsZXMgfD4gCiAgcHVycnI6OmltYXAoc3VtbWFyaXplX2NlbGx0eXBlcykgfD4gCiAgZHBseXI6OmJpbmRfcm93cyguaWQgPSAicHJvamVjdF9pZCIpIHw+IAogICMgYWRkIGluIGRpYWdub3NlcyAKICBkcGx5cjo6bGVmdF9qb2luKGRpYWdub3Nlc19kZiwgYnkgPSAicHJvamVjdF9pZCIpIHw+IAogIGRwbHlyOjptdXRhdGUoCiAgICAjIGNyZWF0ZSBhIGxhYmVsIGZvciBwbG90dGluZwogICAgcHJvamVjdF9sYWJlbCA9IGdsdWU6OmdsdWUoIntwcm9qZWN0X2lkfTp7ZGlhZ25vc2lzfSIpCiAgKQoKYGBgCgojIyBJcyBpdCBhbGwganVzdCBVbmtub3duPwoKVGhlIGZpcnN0IHRoaW5nIHdlIHdpbGwgbG9vayBhdCBpcyBob3cgbWFueSBvZiB0aGUgY2VsbHMgaW4gZWFjaCBzYW1wbGUgYXJlIGNhdGVnb3JpemVkIGFzICJVbmtub3duIiwgd2hpY2ggbWVhbnMgbm8gY29uc2Vuc3VzIGJldHdlZW4gYFNpbmdsZVJgIGFuZCBgQ2VsbEFzc2lnbmAgd2FzIGlkZW50aWZpZWQuIAoKYGBge3IsIGZpZy5oZWlnaHQ9N30KdW5rbm93bl9vbmx5IDwtIGFsbF9yZXN1bHRzX2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIoY29uc2Vuc3VzX2Fubm90YXRpb24gPT0gIlVua25vd24iKQoKZ2dwbG90KHVua25vd25fb25seSwgYWVzKHggPSBwcm9qZWN0X2xhYmVsLCB5ID0gcGVyY2VudF9jZWxsc19hbm5vdGF0aW9uKSkgKwogIGdnZm9yY2U6Omdlb21fc2luYShzaXplID0gMC4xKSArCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxLCB2anVzdCA9IDAuNSksCiAgICAgICAgcGxvdC5tYXJnaW4gPSBtYXJnaW4oMTAsMTAsMTAsMTApKSArCiAgbGFicygKICAgIHggPSAiIiwgCiAgICB5ID0gIlBlcmNlbnQgb2YgY2VsbHMgYW5ub3RhdGVkIGFzIFVua25vd24iCiAgKQogIApgYGAKCkl0IGxvb2tzIGxpa2Ugd2UgZG8gaGF2ZSBzb21lIHNhbXBsZXMgdGhhdCBhcmVuJ3QganVzdCBhbGwgIlVua25vd24iIQpJdCBkZWZpbml0ZWx5IHZhcmllcyBieSBwcm9qZWN0LCBidXQgZm9yIG1vc3QgcHJvamVjdHMgd2UgYXQgbGVhc3Qgc2VlIHNvbWUgcHJvcG9ydGlvbiBvZiBzYW1wbGVzIHdpdGggYXNzaWduZWQgY2VsbCB0eXBlcy4gCgpMZXQncyBsb29rIGF0IGhvdyBtYW55IHNhbXBsZXMgYWN0dWFsbHkgaGF2ZSBzb21lIGNlbGxzIG91dHNpZGUgb2YgdW5rbm93biBpZGVudGlmaWVkLiAKVG8gZG8gdGhpcywgd2Ugd2lsbCBpZGVudGlmeSBhbGwgbGlicmFyaWVzIHRoYXQgb25seSBoYXZlIGNlbGxzIGNhbGxlZCBhcyAiVW5rbm93biIuIAoKYGBge3J9CmhpZ2hfdHVtb3JfZGYgPC0gdW5rbm93bl9vbmx5IHw+IAogIGRwbHlyOjptdXRhdGUobm9fY2VsbHNfaWRlbnRpZmllZCA9IHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbiA9PSAxMDApIHw+IAogIGRwbHlyOjpncm91cF9ieShwcm9qZWN0X2xhYmVsKSB8PiAKICBkcGx5cjo6c3VtbWFyaXplKGFsbF91bmtub3duID0gc3VtKG5vX2NlbGxzX2lkZW50aWZpZWQpLAogICAgICAgICAgICAgICAgICAgY2xhc3NpZmllZF9jZWxscyA9IHN1bSghbm9fY2VsbHNfaWRlbnRpZmllZCksCiAgICAgICAgICAgICAgICAgICBwZXJjZW50YWdlX3Vua25vd24gPSByb3VuZChhbGxfdW5rbm93bi8oYWxsX3Vua25vd24gKyBjbGFzc2lmaWVkX2NlbGxzKSoxMDAsIDIpLAogICAgICAgICAgICAgICAgICAgIyBhZGQgbnVtYmVyIG9mIGxpYnJhcmllcyBmb3IgcGxvdHRpbmcgCiAgICAgICAgICAgICAgICAgICB0b3RhbF9saWJyYXJpZXMgPSBsZW5ndGgobGlicmFyeV9pZCkpIHw+CiAgIyBzZXQgb3JkZXIgZm9yIHBsb3RzIAogIGRwbHlyOjptdXRhdGUocHJvamVjdF9sYWJlbCA9IGZvcmNhdHM6OmZjdF9yZW9yZGVyKHByb2plY3RfbGFiZWwsIHRvdGFsX2xpYnJhcmllcywgLmRlc2MgPSBUUlVFKSkKYGBgCgoKV2hpY2ggcHJvamVjdHMgaGF2ZSB0aGUgaGlnaGVzdCBwcm9wb3J0aW9uIG9mIHNhbXBsZXMgd2l0aCBhbGwgIlVua25vd24iPyAKCmBgYHtyfQojIHRhYmxlIHdpdGggcGVyY2VudGFnZSBvZiBzYW1wbGVzIApoaWdoX3R1bW9yX2RmIHw+IAogIGRwbHlyOjpzZWxlY3QocHJvamVjdF9sYWJlbCwgcGVyY2VudGFnZV91bmtub3duKSB8PiAKICBkcGx5cjo6YXJyYW5nZShkZXNjKHBlcmNlbnRhZ2VfdW5rbm93bikpCgpgYGAKCkl0IGxvb2tzIGxpa2UgYWxsIHByb2plY3RzIGRvIGhhdmUgY2VsbCB0eXBlcyBpZGVudGlmaWVkIHRoYXQgYXJlIG5vdCAiVW5rbm93biIuIApIb3dldmVyLCBgU0NQQ1AwMDAwMTFgIChyZXRpbm9ibGFzdG9tYSksIGhhcyBhIGZhaXJseSBoaWdoIHBlcmNlbnRhZ2Ugb2Ygc2FtcGxlcyB3aXRob3V0IGFueSBjb25zZW5zdXMgbGFiZWxzLiAKCiMjIE51bWJlciBvZiBjZWxsIHR5cGVzIG9ic2VydmVkCgpCZWxvdyB3ZSBsb29rIGF0IHRoZSBudW1iZXIgb2YgY2VsbCB0eXBlcyBvYnNlcnZlZCBpbiBlYWNoIHByb2plY3QgZm9yIGFsbCBzYW1wbGVzLiAKVGhpcyBkb2VzIG5vdCBpbmNsdWRlIGNlbGxzIGxhYmVsZWQgYXMgIlVua25vd24iLiAKCmBgYHtyLCBmaWcuaGVpZ2h0PTEwfQpudW1fY2VsbHR5cGVzX2RmIDwtIGFsbF9yZXN1bHRzX2RmIHw+IAogICMgYWRkIGEgbmV3IGxpbmUgZm9yIGZhY2V0IGxhYmVscyAKICBkcGx5cjo6bXV0YXRlKGZhY2V0X2xhYmVsID0gZ2x1ZTo6Z2x1ZSgie3Byb2plY3RfaWR9XG57ZGlhZ25vc2lzfSIpKSB8PgogICMgcmVtb3ZlIHVua25vd24gYXMgYSBjZWxsIHR5cGUgCiAgZHBseXI6OmZpbHRlcihjb25zZW5zdXNfYW5ub3RhdGlvbiAhPSAiVW5rbm93biIpIHw+IAogIGRwbHlyOjpzZWxlY3QoZmFjZXRfbGFiZWwsIGxpYnJhcnlfaWQsIG51bV9jZWxsdHlwZXMpIHw+IAogIHVuaXF1ZSgpCgpnZ3Bsb3QobnVtX2NlbGx0eXBlc19kZiwgYWVzKHggPSBudW1fY2VsbHR5cGVzKSkgKwogIGdlb21faGlzdG9ncmFtKGJpbndpZHRoID0gMSwgY2VudGVyID0gMCkgKwogIGZhY2V0X3dyYXAodmFycyhmYWNldF9sYWJlbCksIAogICAgICAgICAgICAgbmNvbCA9IDMpICsKICBsYWJzKAogICAgeCA9ICJOdW1iZXIgb2YgY2VsbCB0eXBlcyIKICApICsKICB0aGVtZV9idygpCmBgYAoKIyMgRGlzdHJpYnV0aW9uIG9mIGNvbnNlbnN1cyBjZWxsIHR5cGVzIAoKTm93IHdlIGxvb2sgYXQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgY2VsbCB0eXBlcyBpbiBlYWNoIHNhbXBsZS4gCkZvciB0aGVzZSBwbG90cywgd2Ugd2lsbCBwdWxsIG91dCB0aGUgdG9wIDkgY2VsbCB0eXBlcyBmb3IgZWFjaCBwcm9qZWN0LiAKQWxsIG90aGVyIGNlbGxzIHdpbGwgYmUgbGFiZWxlZCB3aXRoICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLiAKClRoZSB0b3AgY2VsbCB0eXBlcyBhcmUgZGV0ZXJtaW5lZCBieSBjb3VudGluZyBob3cgbWFueSBsaWJyYXJpZXMgZWFjaCBjZWxsIHR5cGUgaXMgZm91bmQgaW4gd2l0aGluIGEgcHJvamVjdCBhbmQgdGFraW5nIHRoZSBtb3N0IGZyZXF1ZW50IHR5cGVzLiAKCmBgYHtyfQpwbG90X2RmIDwtIGFsbF9yZXN1bHRzX2RmIHw+IAogICAgZHBseXI6Omdyb3VwX2J5KHByb2plY3RfaWQpIHw+IAogICAgZHBseXI6Om11dGF0ZSgKICAgICAgIyBnZXQgbW9zdCBmcmVxdWVudGx5IG9ic2VydmVkIGNlbGwgdHlwZXMgYWNyb3NzIGxpYnJhcmllcyBpbiB0aGF0IHByb2plY3QgCiAgICAgIHRvcF9jZWxsdHlwZXMgPSBmb3JjYXRzOjpmY3RfbHVtcF9uKGNvbnNlbnN1c19hbm5vdGF0aW9uLCA5LCBvdGhlcl9sZXZlbCA9ICJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpIHw+IAogICAgICAgICMgc29ydCBieSBmcmVxdWVuY3kgCiAgICAgICAgZm9yY2F0czo6ZmN0X2luZnJlcSgpIHw+IAogICAgICAgICMgbWFrZSBzdXJlIGFsbCByZW1haW5pbmcgYW5kIHVua25vd24gYXJlIGxhc3QsIHVzZSB0aGlzIHRvIGFzc2lnbiBjb2xvcnMgaW4gc3BlY2lmaWMgb3JkZXIKICAgICAgICBmb3JjYXRzOjpmY3RfcmVsZXZlbCgiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIiwgIlVua25vd24iLCBhZnRlciA9IEluZikKICAgICkKCiMgZ2V0IGFsbCB1bmlxdWUgY2VsbCB0eXBlcyBvcmRlcmVkIGJ5IGZyZXF1ZW5jeSAKdW5pcXVlX2NlbGx0eXBlcyA8LSBwbG90X2RmIHw+IAogIGRwbHlyOjpmaWx0ZXIoIXRvcF9jZWxsdHlwZXMgJWluJSBjKCJBbGwgcmVtYWluaW5nIGNlbGwgdHlwZXMiLCAiVW5rbm93biIpKSB8PiAKICBkcGx5cjo6cHVsbCh0b3BfY2VsbHR5cGVzKSB8PiAKICB1bmlxdWUoKSB8PgogIHNvcnQoKSB8PiAKICBhcy5jaGFyYWN0ZXIoKQoKIyBnZXQgY29sb3IgcGFsZXR0ZQpjb2xvcnMgPC0gYygKICBwYWxldHRlLmNvbG9ycyhwYWxldHRlID0gImFscGhhYmV0IiksCiAgImJsYWNrIiwgIyAxIGV4dHJhIHNpbmNlIGFscGhhYmV0IGlzIDI2IGFuZCB3ZSBoYXZlIDI3LCB0aGlzIHdpbGwgYmUgcGxhc21hIGNlbGwgd2hpY2ggc2hvd3MgdXAgb25jZSAKICAiZ3JleTYwIiwgCiAgImdyZXk5NSIKKQpuYW1lcyhjb2xvcnMpIDwtIGModW5pcXVlX2NlbGx0eXBlcywgIkFsbCByZW1haW5pbmcgY2VsbCB0eXBlcyIsICJVbmtub3duIikKYGBgCgoKYGBge3IsIGZpZy5oZWlnaHQ9NjAsIGZpZy53aWR0aD0xMH0KcHJvamVjdF9sYWJlbHMgPC0gdW5pcXVlKGFsbF9yZXN1bHRzX2RmJHByb2plY3RfbGFiZWwpCgojIHN0YWNrZWQgYmFyIGNoYXJ0IHNob3dpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdG9wIDkgY2VsbCB0eXBlcyBmb3IgZWFjaCBwcm9qZWN0LCBpbmNsdWRpbmcgVW5rbm93bgpwcm9qZWN0X2xhYmVscyB8PiAKICBwdXJycjo6bWFwKFwobGFiZWwpewogICAgCiAgICBwcm9qZWN0X2RmIDwtIHBsb3RfZGYgfD4gCiAgICAgIGRwbHlyOjpmaWx0ZXIocHJvamVjdF9sYWJlbCA9PSBsYWJlbCkgfD4gCiAgICAgIGRwbHlyOjptdXRhdGUoCiAgICAgICAgIyByZWxldmVsIGZhY3RvcnMgZm9yIHNwZWNpZmljIHByb2plY3QgCiAgICAgICAgdG9wX2NlbGx0eXBlcyA9IGZvcmNhdHM6OmZjdF9pbmZyZXEodG9wX2NlbGx0eXBlcykgfD4gCiAgICAgICAgICBmb3JjYXRzOjpmY3RfcmVsZXZlbCgiQWxsIHJlbWFpbmluZyBjZWxsIHR5cGVzIiwgIlVua25vd24iLCBhZnRlciA9IEluZikKICAgICAgKQogICAgCiAgICAjIG1ha2UgYSBzdGFja2VkIGJhciBjaGFydCB3aXRoIHRvcCBjZWxsIHR5cGVzIAogICAgZ2dwbG90KHByb2plY3RfZGYpICsgCiAgICAgIGFlcygKICAgICAgICB4ID0gbGlicmFyeV9pZCwgCiAgICAgICAgeSA9IHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbiwgCiAgICAgICAgZmlsbCA9IHRvcF9jZWxsdHlwZXMKICAgICAgKSArCiAgICAgIGdlb21fY29sKCkgKyAKICAgICAgc2NhbGVfeV9jb250aW51b3VzKGV4cGFuZCA9IGMoMCwwKSkgKwogICAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcnMsIG5hbWUgPSAiY2VsbCB0eXBlIikgKwogICAgICBnZ3RpdGxlKGxhYmVsKSArCiAgICAgIHRoZW1lKGF4aXMudGV4dC54ID0gZWxlbWVudF9ibGFuaygpKQogIAogICAgfSkgfD4KICBwYXRjaHdvcms6OndyYXBfcGxvdHMobmNvbCA9IDEpCmBgYAoKClRoaXMgbG9va3MgcmVhbGx5IHByb21pc2luZyEKQSBmZXcgb2JzZXJ2YXRpb25zOiAKCi0gQ2VsbCB0eXBlcyBpZGVudGlmaWVkIHRlbmQgdG8gbGluZSB1cCB3aXRoIGV4cGVjdGF0aW9ucyBmb3IgdGhlIHR5cGUgb2YgdHVtb3IuIApGb3IgZXhhbXBsZSwgbGV1a2VtaWEgbGlicmFyaWVzIGhhdmUgVCBhbmQgQiBjZWxscywgYnJhaW4gdHVtb3JzIGhhdmUgbWFjcm9waGFnZXMsIGFuZCBzb2xpZCB0dW1vcnMgaGF2ZSBmaWJyb2JsYXN0cyBhbmQgbXVzY2xlIGNlbGxzLiAKLSBQcm9qZWN0cyB0aGF0IEkgd291bGQgZXhwZWN0IHRvIGJlIG1vcmUgZGlmZmljdWx0IHRvIGNsYXNzaWZ5IChzYXJjb21hcywgd2lsbXMsIFJCKSBoYXZlIGZld2VyIGNlbGxzIGNsYXNzaWZpZWQgdGhlbiB0aGluZ3MgbGlrZSBicmFpbiBhbmQgbGV1a2VtaWEuIApOb3RhYmx5IG1hbnkgb2YgdGhlIHNvbGlkIHR1bW9yIHByb2plY3RzICg0LCA1LCAxMi0xNiwgYW5kIDIzKSBoYXZlIGEgaGFuZGZ1bCBvZiBQRFggc2FtcGxlcyB3aGVyZSBJIHdvdWxkIGV4cGVjdCB0byBzZWUgZmV3ZXIgbm9ybWFsIGNlbGxzLiAKCiMjIE1vc3QgZnJlcXVlbnRseSBvYnNlcnZlZCBjZWxsIHR5cGVzIAoKVGhlIGxhc3QgdGhpbmcgd2Ugd2lsbCBkbyBpcyBsb29rIGF0IHRoZSBtb3N0IGZyZXF1ZW50bHkgb2JzZXJ2ZWQgY2VsbCB0eXBlcyBhY3Jvc3MgYWxsIHNhbXBsZXMuIApUaGUgYmVsb3cgdGFibGUgaXMgb3JkZXJlZCBieSB0aGUgbnVtYmVyIG9mIGxpYnJhcmllcyB0aGUgY2VsbCB0eXBlIGlzIG9ic2VydmVkLiAKCmBgYHtyfQphbGxfcmVzdWx0c19kZiB8PiAKICBkcGx5cjo6ZmlsdGVyKGNvbnNlbnN1c19hbm5vdGF0aW9uICE9ICJVbmtub3duIikgfD4gCiAgZHBseXI6Omdyb3VwX2J5KGNvbnNlbnN1c19hbm5vdGF0aW9uKSB8PiAKICBkcGx5cjo6c3VtbWFyaXplKAogICAgdG90YWxfbGlicmFyaWVzID0gZHBseXI6Om4oKSwKICAgIG1pbl9wZXJjZW50YWdlID0gbWluKHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbiksCiAgICBtZWFuX3BlcmNlbnRhZ2UgPSByb3VuZChtZWFuKHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbiksIDIpLAogICAgbWVkaWFuX3BlcmNlbnRhZ2UgPSBtZWRpYW4ocGVyY2VudF9jZWxsc19hbm5vdGF0aW9uKSwKICAgIG1heF9wZXJjZW50YWdlID0gbWF4KHBlcmNlbnRfY2VsbHNfYW5ub3RhdGlvbikKICApIHw+IAogIGRwbHlyOjphcnJhbmdlKGRlc2ModG90YWxfbGlicmFyaWVzKSkKICAKYGBgCgoKIyMgU2Vzc2lvbiBpbmZvIAoKYGBge3Igc2Vzc2lvbiBpbmZvfQojIHJlY29yZCB0aGUgdmVyc2lvbnMgb2YgdGhlIHBhY2thZ2VzIHVzZWQgaW4gdGhpcyBhbmFseXNpcyBhbmQgb3RoZXIgZW52aXJvbm1lbnQgaW5mb3JtYXRpb24Kc2Vzc2lvbkluZm8oKQpgYGAKCg==